package quartz.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("quartz")
public class QuartzController {
    final Scheduler scheduler;
    // final AutowireCapableBeanFactory beanFactory;
    final Environment environment;
    final ObjectMapper objectMapper;
    final MetadataReaderFactory metadataReaderFactory;
    final ResourcePatternResolver resourcePatternResolver;

    /**
     * 获取指定包下所有的类的全类名
     *
     * @see ClassPathScanningCandidateComponentProvider#findCandidateComponents(String)
     */
    @GetMapping("jobClassName")
    Object jobClassName() throws IOException, ClassNotFoundException {
        String packageName = "quartz.job.quartz";

        StringBuilder packageSearchPath = new StringBuilder(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX);
        packageSearchPath.append(ClassUtils.convertClassNameToResourcePath(environment.resolveRequiredPlaceholders(packageName)));
        packageSearchPath.append("/**/*.class");

        Resource[] resources = resourcePatternResolver.getResources(packageSearchPath.toString());

        List<String> jobs = new LinkedList<>();
        for (Resource resource : resources) {
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
            Class<?> jClass = Class.forName(metadataReader.getClassMetadata().getClassName());
            if (Job.class.isAssignableFrom(jClass) && !Modifier.isAbstract(jClass.getModifiers())) {
                jobs.add(metadataReader.getClassMetadata().getClassName());
            }
        }
        return jobs;
    }

    /**
     * 创建 Job 和 Trigger，并添加到 scheduler，一个 Job 对应多个 Trigger
     */
    @PostMapping("scheduleJob")
    void scheduleJob(@RequestBody JobAndTrigger j) throws ClassNotFoundException, SchedulerException {
        // if (!CronExpression.isValidExpression(j.cronExpression)) throw new RuntimeException("Illegal cron expression");
        // Job job = (Job) Class.forName(jobClassName).getDeclaredConstructor().newInstance();
        // Job job = (Job) beanFactory.createBean(Class.forName(j.jobClassName), AUTOWIRE_BY_TYPE, false);
        JobDetail jobDetail = scheduler.getJobDetail(JobKey.jobKey(j.jobName, j.jobGroup));
        // 按 cron 表达式和 jobDataMap 构建一个 trigger
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(j.cronExpression);
        TriggerBuilder<CronTrigger> triggerBuilder = TriggerBuilder.newTrigger().withIdentity(j.triggerName, j.triggerGroup).usingJobData(j.jobDataMap).withSchedule(scheduleBuilder);
        if (jobDetail != null) { // 关联到已有的 job
            scheduler.scheduleJob(triggerBuilder.forJob(jobDetail).build());
        } else { // 构建 jobDetail 信息
            jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(j.jobClassName)).withIdentity(j.jobName, j.jobGroup).build();
            scheduler.scheduleJob(jobDetail, triggerBuilder.build());
        }
    }

    @PutMapping("pausejob")
    void pauseJob(@RequestBody JobAndTrigger j) throws Exception { // 暂停 Job
        scheduler.pauseJob(JobKey.jobKey(j.jobName, j.jobGroup));
    }

    @PutMapping("resumejob")
    void resumeJob(@RequestBody JobAndTrigger j) throws Exception { // 恢复 Job
        scheduler.resumeJob(JobKey.jobKey(j.jobName, j.jobGroup));
    }

    @PutMapping("reschedulejob")
    void rescheduleJob(@RequestBody JobAndTrigger j) throws Exception { // 更新 Trigger
        TriggerKey triggerKey = TriggerKey.triggerKey(j.triggerName, j.triggerGroup);
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(j.cronExpression);
        // 按新的 cron 表达式构建 Trigger，但保持原有的 TriggerKey
        Trigger newTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).usingJobData(j.jobDataMap).withSchedule(scheduleBuilder).build();
        scheduler.rescheduleJob(triggerKey, newTrigger); // 替换旧的 Trigger
    }

    @DeleteMapping("deletejob")
    void deleteJob(@RequestBody JobAndTrigger j) throws Exception { // 删除 Job
        JobKey jobKey = JobKey.jobKey(j.jobName, j.jobGroup);
        List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
        for (Trigger trigger : triggers) {
            scheduler.unscheduleJob(trigger.getKey()); // 删除 Trigger
        }
        scheduler.deleteJob(jobKey); // 删除 Job
    }

    @PostMapping("triggerJob")
    void triggerJob(@RequestBody JobAndTrigger j) throws Exception { // 立即执行一次 Job
        scheduler.triggerJob(JobKey.jobKey(j.jobName, j.jobGroup), j.jobDataMap);
    }

    @PutMapping("pauseTrigger")
    void pauseTrigger(@RequestBody JobAndTrigger j) throws Exception { // 暂停 Trigger (取消 Job 和 Trigger 的关联)
        scheduler.pauseTrigger(TriggerKey.triggerKey(j.triggerName, j.triggerGroup));
    }

    @PutMapping("resumeTrigger")
    void resumeTrigger(@RequestBody JobAndTrigger j) throws Exception { // 恢复 Trigger
        scheduler.resumeTrigger(TriggerKey.triggerKey(j.triggerName, j.triggerGroup));
    }

    @DeleteMapping("unscheduleJob")
    void unscheduleJob(@RequestBody JobAndTrigger j) throws Exception { // 删除 Trigger
        scheduler.unscheduleJob(TriggerKey.triggerKey(j.triggerName, j.triggerGroup));
    }

    @GetMapping("queryjob")
    List<JobAndTrigger> queryjob() throws SchedulerException, JsonProcessingException { // 查询 Job
        List<JobAndTrigger> list = new LinkedList<>();
        Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.anyJobGroup());
        for (JobKey jobKey : jobKeys) {
            for (Trigger trigger : scheduler.getTriggersOfJob(jobKey)) {
                JobAndTrigger jt = new JobAndTrigger();
                jt.setJobName(jobKey.getName());
                jt.setJobGroup(jobKey.getGroup());
                JobDetail jobDetail = scheduler.getJobDetail(jobKey);
                jt.setJobClassName(jobDetail.getJobClass().getName());
                jt.setJobDescription(jobDetail.getDescription());
                jt.setTriggerGroup(trigger.getKey().getGroup());
                jt.setTriggerName(trigger.getKey().getName());
                Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                jt.setTriggerState(triggerState.name());
                jt.setTriggerDescription(trigger.getDescription());
                jt.setParam(objectMapper.writeValueAsString(trigger.getJobDataMap()));
                if (trigger instanceof CronTrigger) {
                    CronTrigger cronTrigger = (CronTrigger) trigger;
                    jt.setCronExpression(cronTrigger.getCronExpression());
                    jt.setTimeZoneId(cronTrigger.getTimeZone().getID());
                }
                list.add(jt);
            }
        }
        return list;
    }

    @Getter
    @Setter
    @ToString
    public static class JobAndTrigger {
        String jobName;
        String jobGroup;
        String jobClassName;
        String jobDescription;
        String triggerName;
        String triggerGroup;
        String triggerState;
        // BigInteger repeatInterval;
        // BigInteger timesTriggered;
        String cronExpression;
        String triggerDescription;
        String timeZoneId;
        JobDataMap jobDataMap;
        String param;
    }
}